{{>licenseInfo}}
package {{apiPackage}}


import cask.FormEntry
import io.undertow.server.handlers.form.{FormData, FormParserFactory}

import java.io.File
import scala.jdk.CollectionConverters.*
import java.time.LocalDate
import java.util.UUID
import scala.reflect.ClassTag
import scala.util.*

// needed for BigDecimal params
given cask.endpoints.QueryParamReader.SimpleParam[BigDecimal](BigDecimal.apply)

// a parsed value from an HTTP request
opaque type Parsed[A] = Either[String, A]

object Parsed {
  def apply[A](value: A): Parsed[A] = Right(value)

  def eval[A](value: => A): Parsed[A] = Try(value) match {
    case Failure(exp) => Left(s"Error: ${exp.getMessage}")
    case Success(ok) => Right(ok)
  }

  def fromTry[A](value : Try[A]) = value match {
      case Failure(err) => Left(err.getMessage)
      case Success(ok) => Right(ok)
  }

  def fail[A](msg: String): Parsed[A] = Left(msg)

    def optionalValue(map: Map[String, collection.Seq[String]], key: String): Parsed[Option[String]] = {
        map.get(key) match {
            case Some(Seq(only: String)) => Parsed(Option(only))
            case Some(Seq()) => Parsed(None)
            case Some(many) => Parsed.fail(s"${many.size} values set for '$key'")
            case None => Parsed(None)
        }
    }

    def singleValue(map: Map[String, collection.Seq[String]], key : String): Parsed[String] = {
        map.get(key) match {
            case Some(Seq(only : String)) => Parsed(only)
            case Some(Seq()) => Parsed("")
            case Some(many) => Parsed.fail(s"${many.size} values set for '$key'")
            case None => Parsed.fail(s"required '$key' was not set")
        }
    }
    
    def manyValues(map: Map[String, collection.Seq[String]], key : String, required: Boolean): Parsed[List[String]] = {
      map.get(key) match {
        case Some(many) => Parsed(many.toList)
        case None if required => Parsed.fail(s"required '$key' was not set")
        case None => Parsed(Nil)
      }
    }
}

extension[A] (parsed: Parsed[A]) {
  def toEither: Either[String, A] = parsed

  def asLong(using ev : A =:= String): Parsed[Long] = as[Long](_.toLongOption)
  def asBoolean(using ev : A =:= String): Parsed[Boolean] = as[Boolean](_.toBooleanOption)
  def asInt(using ev : A =:= String): Parsed[Int] = as[Int](_.toIntOption)
  def asByte(using ev : A =:= String): Parsed[Byte] = as[Byte](_.toByteOption)
  def asUuid(using ev : A =:= String): Parsed[UUID] = as[UUID](x => Try(UUID.fromString(x)).toOption)
  def asFloat(using ev : A =:= String): Parsed[Float] = as[Float](_.toFloatOption)
  def asDouble(using ev : A =:= String): Parsed[Double] = as[Double](_.toDoubleOption)
  def asDate(using ev: A =:= String): Parsed[LocalDate] = as[LocalDate](x => Try(LocalDate.parse(x)).toOption)

  private def as[B : ClassTag](f : String => Option[B])(using ev : A =:= String): Parsed[B] = parsed.flatMap { str =>
    f(ev(str)) match {
      case None => Parsed.fail(s"'$str' cannot be parsed as a ${implicitly[ClassTag[B]].runtimeClass}")
      case Some(x) => Parsed(x)
    }
  }


  def mapError(f : String => String) : Parsed[A] = parsed match {
    case Left(msg) => Left(f(msg))
    case right => right
  }

  def map[B](f: A => B): Parsed[B] = parsed match {
    case Right(value) => Right(f(value))
    case Left(err) => Left(err)
  }
  def flatMap[B](f : A => Parsed[B]): Parsed[B] = parsed match {
    case Right(value) => f(value)
    case Left(err) => Left(err)
  }
}


extension (request: cask.Request) {

  def formSingleValueRequired(paramName: String): Parsed[String] = {
    val data = formDataForKey(paramName).map(_.getValue).toSeq
    Parsed.singleValue(Map(paramName -> data), paramName)
  }
  def formSingleValueOptional(paramName: String): Parsed[Option[String]] = {
    val data = formDataForKey(paramName).map(_.getValue).toSeq
    Parsed.optionalValue(Map(paramName -> data), paramName)
  }

    def formValueAsFileOptional(paramName: String): Parsed[Option[File]] = {
        val data = formDataForKey(paramName)
        data.map(_.getFileItem.getFile.toFile).toSeq match {
            case Seq() => Parsed(None)
            case Seq(file) => Parsed(Option(file))
            case many => Parsed.fail(s"${many.size} file values set for '$paramName'")
        }
    }

  def formValueAsFileRequired(paramName: String): Parsed[File] = {
    val data = formDataForKey(paramName)
    data.map(_.getFileItem.getFile.toFile).toSeq match {
      case Seq() => Parsed.fail(s"No file form data was submitted for '$paramName'. The submitted form keys were: ${formDataKeys.mkString(",")}")
      case Seq(file) => Parsed(file)
      case many => Parsed.fail(s"${many.size} file values set for '$paramName'")
    }
  }

  def formManyValues(paramName: String, required: Boolean): Parsed[List[String]] = {
    val data = formDataForKey(paramName).map(_.getValue).toSeq
    Parsed.manyValues(Map(paramName -> data), paramName, required)
  }

  def formData: FormData = FormParserFactory.builder().build().createParser(request.exchange).parseBlocking()

  def formDataKeys: Iterator[String] = formData.iterator().asScala

  def formDataForKey(paramName: String): Iterable[FormData.FormValue] = formData.get(paramName).asScala
  
  def headerSingleValueOptional(paramName: String): Parsed[Option[String]] = Parsed.optionalValue(request.headers, paramName)
  def headerSingleValueRequired(paramName: String): Parsed[String] = Parsed.singleValue(request.headers, paramName)

  def headerManyValues(paramName: String, required: Boolean): Parsed[List[String]] = Parsed.manyValues(request.headers, paramName, required)

  def bodyAsString = new String(request.readAllBytes(), "UTF-8")
  
  def pathValue(index: Int, paramName: String, required : Boolean): Parsed[String] = {
    request
      .remainingPathSegments
      .lift(index) match {
      case Some(value) => Right(value)
      case None if required => Left(s"'$paramName'' is a required path parameter at path position $index")
      case None             => Right("")
    }
  }
}
